package com.n.passwordbook.Utils;

import com.n.passwordbook.PasswordBook.PasswordBook;
import org.apache.commons.crypto.cipher.CryptoCipher;
import org.apache.commons.crypto.utils.Utils;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.Properties;

public class CryptographyHelper
{
    private static final String TransformType = "AES/CBC/PKCS5Padding ";

    public static byte[] convertPasswordToKey(String password)
    {
        MessageDigest sha384 = null;
        try
        {
            sha384 = MessageDigest.getInstance("SHA-384");
            byte[] mixedPassword = getSHA512(password);
            byte[] rawPassword = password.getBytes(StandardCharsets.UTF_8);
            byte[] bytePassword = new byte[mixedPassword.length + rawPassword.length];
            for (int i = 0; i < 64; ++i)
            {
                bytePassword[i] = mixedPassword[i];
            }
            for (int i = 64, j = 0; i < bytePassword.length; ++i, ++j)
            {
                bytePassword[i] = rawPassword[j];
            }
            sha384.update(bytePassword);
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        }
        return sha384.digest();
    }

    private static byte[] getSHA512(String txt)
    {
        try
        {
            MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
            sha512.update(txt.getBytes(StandardCharsets.UTF_8));
            return sha512.digest();
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
            return null;
        }
    }

    public static String encryptStringByAES256(String content, byte[] key)
    {
        SecretKeySpec sKey = new SecretKeySpec(getAES256Key(key), "AES");
        IvParameterSpec sIV = new IvParameterSpec(getAESIV(key));
        Properties properties = new Properties();
        final ByteBuffer outBuffer;
        final int bufferSize = 1024;
        final int updateBytes;
        final int finalBytes;
        //Creates a CryptoCipher instance with the transformation and properties.
        try (CryptoCipher encipher = Utils.getCipherInstance(TransformType, properties))
        {

            ByteBuffer inBuffer = ByteBuffer.allocateDirect(bufferSize);
            outBuffer = ByteBuffer.allocateDirect(bufferSize);
            inBuffer.put(content.getBytes(StandardCharsets.UTF_8));

            inBuffer.flip(); // ready for the cipher to read it

            // Initializes the cipher with ENCRYPT_MODE,key and iv.
            encipher.init(Cipher.ENCRYPT_MODE, sKey, sIV);

            // Continues a multiple-part encryption/decryption operation for byte buffer.
            updateBytes = encipher.update(inBuffer, outBuffer);

            // We should call do final at the end of encryption/decryption
            finalBytes = encipher.doFinal(inBuffer, outBuffer);

            outBuffer.flip(); // ready for use as decrypt
        }
        catch (IOException | InvalidKeyException | ShortBufferException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | InternalError e)
        {
            e.printStackTrace();
            return "";
        }
        byte[] encoded = new byte[updateBytes + finalBytes];
        outBuffer.duplicate().get(encoded);
        String encodedString = Base64.getUrlEncoder().encodeToString(encoded);
        return encodedString;
    }

    public static String decryptStringByAES256(String content, byte[] key)
    {
        SecretKeySpec sKey = new SecretKeySpec(getAES256Key(key), "AES");
        IvParameterSpec sIV = new IvParameterSpec(getAESIV(key));
        Properties properties = new Properties();
        final ByteBuffer outBuffer;
        final int bufferSize = 1024;
        ByteBuffer decoded = ByteBuffer.allocateDirect(bufferSize);
        //Creates a CryptoCipher instance with the transformation and properties.
        try (CryptoCipher decipher = Utils.getCipherInstance(TransformType, properties))
        {
            decipher.init(Cipher.DECRYPT_MODE, sKey, sIV);
            outBuffer = ByteBuffer.allocateDirect(bufferSize);
            outBuffer.put(Base64.getUrlDecoder().decode(content));
            outBuffer.flip();
            decipher.update(outBuffer, decoded);
            decipher.doFinal(outBuffer, decoded);
            decoded.flip(); // ready for use
        }
        catch (IOException | InvalidKeyException | ShortBufferException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | InternalError e)
        {
            e.printStackTrace();
            return "";
        }
        final ByteBuffer copy = decoded.duplicate();
        final byte[] bytes = new byte[copy.remaining()];
        copy.get(bytes);
        return new String(bytes, StandardCharsets.UTF_8);
    }

    private static byte[] getAES256Key(byte[] key)
    {
        byte[] returnKey = new byte[32];
        for (int i = 0; i < 32; ++i)
        {
            returnKey[i] = key[i];
        }
        return returnKey;
    }

    private static byte[] getAESIV(byte[] key)
    {
        byte[] returnKey = new byte[16];
        for (int i = 32, j = 0; i < 48; ++i, ++j)
        {
            returnKey[j] = key[i];
        }
        return returnKey;
    }

    public static void setPBKey(String password)
    {
        PasswordBook.PBKey = convertPasswordToKey(password);
    }
}
